/**
 * 
 */
package ua.lviv.franko.solvers.oneDimention;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

import ua.lviv.franko.integration.ConstantFunction1D;
import ua.lviv.franko.integration.GaussQuadrature;
import ua.lviv.franko.integration.Quadrature;
import ua.lviv.franko.matrix.RowType;
import ua.lviv.franko.matrix.hash.lu.HashMatrixLUStyle;
import ua.lviv.franko.matrix.hash.lu.LUFactorization;
import ua.lviv.franko.mlsFunctions.oneDimension.BaseFunction;
import ua.lviv.franko.mlsFunctions.oneDimension.Particle;
import ua.lviv.franko.mlsFunctions.oneDimension.Values;
import ua.lviv.franko.mlsFunctions.oneDimension.weightFunctions.CubicSpline;
import ua.lviv.franko.mlsFunctions.oneDimension.weightFunctions.IWeightFunction;
import ua.lviv.franko.visualisation.NewOkCancelDialog;

/**
 * @author marko.bekhta
 * 
 */
public class DiffusionMFSolver {

	// general data
	protected static final int		QUADRATURE_POINTS_NUMBER		= 7;
	protected static final int		NUMBER_OF_INTEGRATION_INTERVALS	= 2;
	protected static final int		NUMBER_OF_UNKNOWNS				= 1;
	protected Quadrature			gau;
	protected EquationParameters	parameters;
	protected HashMatrixLUStyle		mainMatrix;
	protected double[]				b;
	protected double				leftBoundery;
	protected double				rightBoundery;
	protected BoundaryCondition		leftBC;
	protected BoundaryCondition		rightBC;

	// data for base functions
	protected List<Particle>		particles;
	protected List<BaseFunction>	baseFunctions;

	/**
	 * 
	 * @param parameters
	 *            - equation parameters
	 * @param particles
	 *            - list of particles
	 * @param leftBoundery
	 *            - left side of domain
	 * @param rightBoundery
	 *            - right side of domain
	 * @param PolinomialDegree
	 *            - degree of polynomials used for basis
	 * @param weight
	 *            - weight function
	 */
	public DiffusionMFSolver(final EquationParameters parameters, List<Particle> particles, double leftBoundery, double rightBoundery, int PolinomialDegree, IWeightFunction weight) {

		super();
		this.parameters = parameters;
		this.particles = particles;
		this.rightBoundery = rightBoundery;
		this.leftBoundery = leftBoundery;

		gau = new GaussQuadrature(QUADRATURE_POINTS_NUMBER);

		// creation of base functions;
		this.createBaseFunctions(PolinomialDegree, weight);
	}

	public void generateMatrix() {
		int tmpN = 0;
		if (leftBC.isDirihlet()) {
			tmpN++;
		}
		if (rightBC.isDirihlet()) {
			tmpN++;
		}
		mainMatrix = new HashMatrixLUStyle(RowType.ARRAY_TYPE, (particles.size() + tmpN) * NUMBER_OF_UNKNOWNS);
		b = new double[(particles.size() + tmpN) * NUMBER_OF_UNKNOWNS];

		for (int i = 0; i < particles.size(); i++) {
			// System.out.println(i);
			BaseFunction baseFunction = baseFunctions.get(i);
			double[][] vals = this.gaussQuadratureIntegration(NUMBER_OF_INTEGRATION_INTERVALS,
					Math.max(baseFunction.getXi().getXi() - baseFunction.getXi().getRho(), leftBoundery),
					Math.min(baseFunction.getXi().getXi() + baseFunction.getXi().getRho(), rightBoundery), baseFunction);
			int pos1 = particles.indexOf(baseFunction.getXi());
			for (int j = 0; j < baseFunction.getNodeList().size(); j++) {
				int pos2 = particles.indexOf(baseFunction.getNodeList().get(j));

				mainMatrix.setValue(pos1, pos2, vals[j * NUMBER_OF_UNKNOWNS][0]);

				mainMatrix.setValue(pos2, pos1, vals[j * NUMBER_OF_UNKNOWNS][1]);

			}
		}
	}

	public BoundaryCondition getLeftBC() {
		return leftBC;
	}

	public BoundaryCondition getRightBC() {
		return rightBC;
	}

	public void setLeftBC(BoundaryCondition leftBC) {
		this.leftBC = leftBC;
	}

	public void setRightBC(BoundaryCondition rightBC) {
		this.rightBC = rightBC;
	}

	public EFGDiffusionResult1D solve() {
		if (leftBC.isDirihlet()) {
			BaseFunction bf = baseFunctions.get(0);
			// TODO
			double[] vals = bf.calculateAllFunctionsValuesOnly(leftBoundery);
			int size = mainMatrix.getN();
			for (int i = 0; i < vals.length; i++) {
				mainMatrix.setValue(size - 1, i, vals[i]);

				mainMatrix.setValue(i, size - 1, vals[i]);
			}

			b[size - 1] = leftBC.getValue();

			if (rightBC.isDirihlet()) {
				bf = baseFunctions.get(baseFunctions.size() - 1);
				vals = bf.calculateAllFunctionsValuesOnly(rightBoundery);
				for (int i = 0; i < vals.length; i++) {
					mainMatrix.setValue(size - 2, (i + baseFunctions.size() - vals.length), vals[i]);

					mainMatrix.setValue((i + baseFunctions.size() - vals.length), size - 2, vals[i]);
				}
				b[size - 2] = rightBC.getValue();
			}
		} else {
			if (rightBC.isDirihlet()) {
				BaseFunction bf = baseFunctions.get(baseFunctions.size() - 1);
				double[] vals = bf.calculateAllFunctionsValuesOnly(rightBoundery);
				int size = mainMatrix.getN();
				for (int i = 0; i < vals.length; i++) {
					mainMatrix.setValue(size - 1, i, vals[i]);

					mainMatrix.setValue(i, size - 1, vals[i]);
				}

				b[size - 1] = rightBC.getValue();

			}
		}

		HashMatrixLUStyle.printMatrix(mainMatrix);
		System.out.println();
		System.out.println();
		HashMatrixLUStyle.printMatrix(b);
		// System.out.println();
		// System.out.println();
		LUFactorization lu = new LUFactorization(this.mainMatrix);
		double[] results = lu.calculate(b);
		// System.out.println();
		// System.out.println();
		// HashMatrixLUStyle.printMatrix(results);
		return new EFGDiffusionResult1D(results, baseFunctions, particles);
	}

	private void createBaseFunctions(int polinomialDegree, IWeightFunction weight) {
		baseFunctions = new ArrayList<BaseFunction>();
		for (Iterator<Particle> iterator = particles.iterator(); iterator.hasNext();) {
			Particle particle = iterator.next();
			BaseFunction bf = new BaseFunction(polinomialDegree, particle, weight);
			bf.setNodeList(this.getParticleSupport(particle, weight));
			this.baseFunctions.add(bf);

			XYSeriesCollection col = new XYSeriesCollection();
			for (int j = 0; j < bf.getNodeList().size(); j++) {
				XYSeries ser = new XYSeries("[i].valDerivaty", true, true);
				XYSeries ser2 = new XYSeries("calculateDerivaty", true, true);
				double xxxxxxx = leftBoundery;
				while (xxxxxxx <= rightBoundery + 0.0001) {
					double tmp1 = bf.calculateAllFunctions(xxxxxxx)[j].valDerivaty;
					double tmp2 = bf.calculateDerivaty(xxxxxxx);
					ser.add(xxxxxxx, tmp1);
					ser2.add(xxxxxxx, tmp2);
					xxxxxxx += 0.002;
				}
				col.addSeries(ser);
				col.addSeries(ser2);
			}

			NewOkCancelDialog d = new NewOkCancelDialog(null, false, col);
			d.setLocationRelativeTo(null);
			d.setVisible(true);
		}

	}

	private double[][] gaussQuadratureIntegration(int n, double ax, double bx, BaseFunction baseFunction) {
		double hx = (bx - ax) / n;

		double[][] result = new double[baseFunction.getNodeList().size() * NUMBER_OF_UNKNOWNS][2 * NUMBER_OF_UNKNOWNS];

		for (int i = 0; i < n; i++) {
			double[][] tmp = gaussQuadratureIntegration(ax + i * hx, ax + (i + 1) * hx, baseFunction);
			for (int k = 0; k < tmp.length; k++) {
				for (int q = 0; q < tmp[0].length; q++) {
					result[k][q] += tmp[k][q];
				}
			}
		}

		return result;
	}

	private double[][] gaussQuadratureIntegration(double ax, double bx, BaseFunction baseFunction) {
		// first column - baseFunction*v; second column - v*baseFunction
		double[][] result = new double[baseFunction.getNodeList().size() * NUMBER_OF_UNKNOWNS][2 * NUMBER_OF_UNKNOWNS];
		// its a position of main node - node of base function
		int pos = baseFunction.getNodeList().indexOf(baseFunction.getXi());

		double f = 0.0;

		for (int i = 0; i < QUADRATURE_POINTS_NUMBER; i++) {
			double xi = (ax + bx) / 2 + (bx - ax) / 2 * gau.getX(i);
			Values[] values = baseFunction.calculateAllFunctions(xi);
			double fiDer = values[pos].valDerivaty;
			double fiVal = values[pos].val;

			for (int j = 0; j < values.length; j++) {
				double fjDer = values[j].valDerivaty;
				double fjVal = values[j].val;

				result[j][0] += gau.getW(i) * parameters.p.calculate(xi) * fiDer * fjDer + parameters.w.calculate(xi) * fjDer * fiVal + parameters.q.calculate(xi) * fiVal * fjVal;
				result[j][1] += gau.getW(i) * parameters.p.calculate(xi) * fiDer * fjDer + parameters.w.calculate(xi) * fjVal * fiDer + parameters.q.calculate(xi) * fiVal * fjVal;
			}
			f += gau.getW(i) * fiVal * parameters.f.calculate(xi);

		}
		double bMinusA = (bx - ax) / 2.0;
		// f *= (bx - ax) / 2.0;
		f *= bMinusA;

		for (int i = 0; i < result.length; i++) {
			result[i][0] *= bMinusA;
			result[i][1] *= bMinusA;
		}

		b[particles.indexOf(baseFunction.getXi())] = f;
		return result;
	}

	private List<Particle> getParticleSupport(Particle particle, IWeightFunction weight) {
		List<Particle> list = new ArrayList<Particle>();
		for (Iterator<Particle> iterator = particles.iterator(); iterator.hasNext();) {
			Particle p = (Particle) iterator.next();
			if (weight.intersectedSupport(particle, p)) {
				list.add(p);
			}
		}
		return list;
		// return particles;
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		EquationParameters eqP = new EquationParameters(new ConstantFunction1D(1.0), new ConstantFunction1D(100.0), new ConstantFunction1D(0.0), new ConstantFunction1D(100.0));
		double a = 0.0;
		double b = 1.0;
		int n = 10;
		double hStep = (b - a) / (double) n;
		double rho = 1.001 * hStep;
		List<Particle> particles = new ArrayList<Particle>();

		for (int i = 0; i <= n; i++) {
			particles.add(new Particle(a + i * hStep, rho));
		}

		int PolinomialDegree = 1;
		IWeightFunction weight = new CubicSpline();
		DiffusionMFSolver solver = new DiffusionMFSolver(eqP, particles, a, b, PolinomialDegree, weight);
		solver.setLeftBC(new BoundaryCondition(0, true));
		solver.setRightBC(new BoundaryCondition(0, true));
		long start = System.nanoTime();
		solver.generateMatrix();
		EFGDiffusionResult1D diffusionResult1D = solver.solve();
		System.out.println("DiffusionMFSolver - " + (System.nanoTime() - start));
		diffusionResult1D.display(a, b);
	}

}
